const Koa = require('koa2')
const app = new Koa()
const cors = require('koa2-cors')
const http = require('http')
const server = http.createServer(app.callback())
const socketIo = require('socket.io')
const mediasoup = require('mediasoup')

// 允许跨域
app.use(cors())

const host = '127.0.0.1'
const port = 8099
server.listen(port, () => {
    console.log(`服务器运行在${host}:${port}`)
})

const io = socketIo(server, { cors: true })


//声明worker--->工作站点
let worker
let workerRouter

// 使用 Map 来存储和管理每个房间的用户及其人数限制
const roomMap = {} // { roomName房间号: {用户uid: [socket.id, socket, roomName房间名称] } }
const roomPeople = {} // { roomName房间号:  maxPeople规定房间人数 }
let rooms = {}          // 房间对象 { roomName房间号: { router房间路由：workerRouter , socketIdArr用户连接组: [socket.id] }}
let peers = {}          // 连接对象 { socketId用户连接ID: {roomName房间号, uid用户ID, socket用户连接, transports连接通道ID组, producers生产者ID组, consumers生产者ID组 }}
let TArr = []           // 通道组   [ { uid, socketId用户连接, roomName房间号, transport通道, consumer消费者 } ]
let producerArr = []    // 生产者组 [ { uid, socketId, roomName, producer}]
let consumerArr = []    // 消费者组 [ { uid, socketId, roomName, consumer}]


// mediaCodecs媒体编解码器----这是rtp功能的数组
const mediaCodecs = [
    // kind种类，mime类型，clockRate时钟频率，parameters参数，x-google-起始比特率
    { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, },
    { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, parameters: { 'x-google-start-bitrate': 1000, }, }
]

// WebRTC通道的选项
const webRtcT_Options = {
    listenIps: [{ ip: host }],
    enableUdp: true,
    enableTcp: true,
    preferUdp: true,
}


//构造一个工作站点的类函数
const createWorker = async () => {
    // 人数 = 通道数/2 + 1 = 端口数开方 - 1。 //默认80人需要6561个端口，现在有8000个端口
    worker = await mediasoup.createWorker({ rtcMinPort: 1000, rtcMaxPort: 9000 })
    // console.log(`工作站点-进程： ${worker.pid}`)
    worker.on('died', error => {
        console.error('工作站点出问题' + error)
        //2 秒后退出
        setTimeout(() => process.exit(1), 2000)
    })
    return worker
}

//创建工作站点
worker = createWorker()

io.on('connection', async (socket) => {
    // socket连接成功
    console.log(`${socket.id} --- 连接成功 ---`)

    // 01--监听房间是否人满 
    socket.on('full', ({ uid, roomName }, callback) => {
        // console.log(uid, roomName)

        // 检查房间是否存在
        let roomInfo = roomMap[roomName]

        if (roomInfo) {
            // 房间已存在，检查该用户是否已在房间中
            if (roomInfo[uid]) {
                // 用户已在房间中
                return callback({ data: '您已经在此房间中' })
            }
            // 用户不在房间中，检查房间人数（减去maxPeople属性） 
            const currentPeople = Object.keys(roomInfo).length
            // console.log(JSON.stringify(roomPeople))
            // 如果房间存在，就一定存在maxPeople，查找房间允许有几个人
            // 获取房间对应的最大人数
            const roomMaxPeople = roomPeople[roomName]
            console.log(roomName, currentPeople, roomMaxPeople)
            // 如果当前人数大于最大人数，房间满了
            if (currentPeople > roomMaxPeople) {
                return callback({ data: '该房间人数已满' })
            } else {
                // 房间未满，可以加入
                return callback({ data: '可加入该房间' })
            }
        } else {
            // 房间不存在，返回错误信息或直接创建新房间
            return callback({ data: '房间不存在' })
        }
    })



    // 02--监听加入房间 
    socket.on('joinRoom', async ({ uid, roomName, maxPeople }) => {
        // 获取房间信息
        let roomInfo = roomMap[roomName]
        let roomp = roomPeople[roomName]
        // console.log(roomInfo, roomp)
        if (roomInfo === undefined || roomp === undefined) {
            // 如果房间不存在，则创建新房间
            roomInfo = { uid: [socket.id, socket, roomName] }
            roomMap[roomName] = roomInfo
            // 如果房间人数不存在，则第一次规定房间人数
            roomPeople[roomName] = maxPeople
        }
        // 添加用户到房间
        roomInfo[uid] = [socket.id, socket, roomName]
        console.log(`用户 ${uid} 加入了房间 ${roomName}`)

        // 获取房间对应的最大人数
        // const roomMaxPeople = roomPeople[roomName]
        // console.log(`${roomName} 房间规定人数为 ${roomMaxPeople}`)

        //创建rooms房间对象
        let socketIdArr = []
        //在房间对象里找当前的房间
        if (rooms[roomName]) {
            // 如果此房间存在，获取原有的workerRouter，workerRouter
            workerRouter = rooms[roomName].router
            socketIdArr = rooms[roomName].socketIdArr
        } else {
            // 如果没有此房间，先根据worker工作站点的方法创建一个路由
            workerRouter = await worker.createRouter({ mediaCodecs, })
        }

        // 创建此房间
        rooms[roomName] = {
            router: workerRouter,
            socketIdArr: [...socketIdArr, socket.id],
        }
        // console.log(`房间路由Rtp功能: ${JSON.stringify(workerRouter.rtpCapabilities)}`)
        // console.log(`房间路由ID: ${workerRouter.id}`)
        // console.log(`房间用户连接组个数: ${rooms[roomName].socketIdArr.length}`)


        // 创建peers连接对象
        peers[socket.id] = {
            uid,
            socket,
            roomName,
            transports: [],
            producers: [],
            consumers: []
        }

        // 02--获取房间路由的Rtp功能,发送给前端
        const rtpCap = workerRouter.rtpCapabilities
        // 从客户端调用回调并发回 rtpCapabilities
        socket.emit('getRtpCap', rtpCap)
    })


    // 03--监听创建webRTC通道，区分生产者和消费者传输
    socket.on('creWebRtcT', async ({ consumer }, callback) => {
        // 从peers中获取房间号
        const { roomName, uid } = peers[socket.id]

        // 根据房间号获取此房间在rooms的路由
        const router = rooms[roomName].router

        //给创建webRTC的函数传入当前房间的路由
        creWebRtcT(router).then(transport => {
            callback({
                // webRTC通道的参数
                paramsT: {
                    id: transport.id,
                    iceParameters: transport.iceParameters,
                    iceCandidates: transport.iceCandidates,
                    dtlsParameters: transport.dtlsParameters,
                }
            })
            // 将通道添加到peers
            addT(transport, roomName, consumer, uid)
        }, error => { console.log("creWebRtcTErr", error) })
    })

    // 04--监听生产者连接通道的方法
    socket.on('producerT-connect', ({ dtlsParameters }) => {
        // console.log('前端生产者DTLS参数: ', { dtlsParameters })
        // 根据socketId获取通道进行连接
        getTransport(socket.id).connect({ dtlsParameters })
    })

    // 04.1--监听生产者发送通道生产的方法
    socket.on('producerT-produce', async ({ appData, kind, rtpParameters }, callback) => {
        // 根据socketId获取通道进行生产，赋值给一个生产者
        const producer = await getTransport(socket.id).produce({ kind, rtpParameters })
        const { roomName, uid } = peers[socket.id]
        // 将此生产添加到pees对象和TArr数组里
        addProducer(producer, roomName, uid)
        //找出用户Id(这与生产者ID无关）
        // console.log(producer.id)
        let newUName = uid
        //告知消费者函数
        tellConsumers(socket.id, producer.kind, producer.id, roomName, newUName)

        //生产通道关闭
        producer.on('transportclose', () => {
            // console.log('生产者的已关闭')
            producer.close()
        })
        // 将生产者的 ID 返回给客户端
        callback({
            id: producer.id,
            producerExist: producerArr.length > 1 ? true : false
        })
    })

    //04.2--监听获取所有生产者连接通道的方法
    socket.on('getProducersT', callback => {
        const { roomName, uid } = peers[socket.id]
        let producerAVIds = []
        let producerUid = []
        let producerUidAVIds = []
        producerArr.forEach(producerData => {
            if (producerData.uid !== uid && producerData.socketId !== socket.id && producerData.roomName === roomName) {
                producerAVIds.push(producerData.producer.id)
                producerUid.push(producerData.uid)
                producerUidAVIds.push({ [producerData.uid]: producerData.producer.id })
            }
        })
        // console.log(producerUidAVIds)
        // 将生产者列表返回给客户端
        callback(producerUid, producerAVIds, producerUidAVIds)
    })

    // 06--监听消费者连接接收通道的方法
    socket.on('consumerT-connect', async ({ dtlsParameters, consumerTId }) => {
        // console.log(`前端消费者DTLS参数: ${dtlsParameters}`)
        const consumerTransportData = TArr.find(transportData => (
            transportData.transport.id === consumerTId && transportData.consumer && transportData.socketId === socket.id
        ))
        if (!consumerTransportData) {
            // console.log('未找到对应的消费者传输')
            return
        }
        const consumerTransport = consumerTransportData.transport
        // 消费通道进行连接
        await consumerTransport.connect({ dtlsParameters })
    })


    // 07--监听消费者消耗的方法
    socket.on('consumerT-consume', async ({ rtpCapabilities, newProducerId, consumerTId }, callback) => {
        try {
            const { roomName, uid } = peers[socket.id]
            const router = rooms[roomName].router
            let consumerTransport = TArr.find(transportData => (
                transportData.consumer && transportData.transport.id == consumerTId
            )).transport

            // 根据路由检查房间是否可以使用指定的生产者
            if (router.canConsume({
                producerId: newProducerId,
                rtpCapabilities
            })) {
                // 获取通道进行消费，赋值给一个消费者，paused暂停
                const consumer = await consumerTransport.consume({
                    producerId: newProducerId,
                    rtpCapabilities,
                    paused: true,
                })
                //消费通道关闭
                consumer.on('transportclose', () => {
                    console.log('消费通道关闭')
                })
                // 其他生产者对该消费者的生产通道关闭
                consumer.on('producerclose', () => {
                    // console.log('其他生产者对该消费者的生产通道关闭')
                    socket.emit('producerT-close', { newProducerId })

                    consumerTransport.close([])
                    TArr = TArr.filter(transportData => transportData.transport.id !== consumerTransport.id)
                    consumer.close()
                    consumerArr = consumerArr.filter(consumerData => consumerData.consumer.id !== consumer.id)
                })

                addConsumer(consumer, roomName, uid)

                // 从消费者处提取通道参数
                const paramsT = {
                    id: consumer.id,
                    producerId: newProducerId,
                    kind: consumer.kind,
                    rtpParameters: consumer.rtpParameters,
                    consumerId: consumer.id,
                }
                // 发送参数给客户端
                callback({ paramsT })
            }
        } catch (error) {
            console.log("consumeErr:", error)
            callback({
                paramsT: {
                    error: error
                }
            })
        }
    })

    // 08--监听消费者恢复的方法
    socket.on('consumer-resume', async ({ consumerId }) => {
        // console.log('消费者恢复')
        const { consumer } = consumerArr.find(consumerData => consumerData.consumer.id === consumerId)
        await consumer.resume()
    })

    //监听用户离开房间
    socket.on('leaveRoom', async ({ uid, roomName }) => {
        console.log(`用户${uid}带着连接${socket.id} 离开房间${roomName}`)

        // 删除房间中的用户信息
        let roomInfo = roomMap[roomName]
        if (roomInfo) {
            delete roomInfo[uid]
            // 如果房间为空，删除该房间
            if (Object.keys(roomInfo).length === 0) {
                delete roomMap[roomName]
                delete rooms[roomName]
            } else {
                roomMap[roomName] = roomInfo
            }
            delete roomPeople[roomName]
        }

        // 关闭生产者和消费者
        consumerArr = removeItems(consumerArr, uid, socket.id, 'consumer')
        producerArr = removeItems(producerArr, uid, socket.id, 'producer')
        TArr = removeItems(TArr, uid, socket.id, 'transport')

        delete peers[socket.id]

        // 如果rooms房间为空，删除该房间
        if (Object.keys(rooms).length === 0) {
            console.log(`通过rooms房间 ${roomName} 已经被清空并删除`)
        } else {
            // 从房间里移除socketId
            rooms[roomName] = {
                router: rooms[roomName].router,
                socketIdArr: rooms[roomName].socketIdArr.filter(socketId => socketId !== socket.id)
            }

        }
    })

    socket.on('disconnect', () => {
        if (peers[socket.id]) {
            const { uid, roomName } = peers[socket.id]
            // console.log(`用户${uid}带着连接${socket.id}异常关闭`)
            // 删除房间中的用户信息
            let roomInfo = roomMap[roomName]
            if (roomInfo) {
                delete roomInfo[uid]
                // 如果房间为空，删除该房间
                if (Object.keys(roomInfo).length === 0) {
                    delete roomMap[roomName]
                    delete rooms[roomName]
                } else {
                    roomMap[roomName] = roomInfo
                }
                delete roomPeople[roomName]
            }

            consumerArr = removeItems(consumerArr, uid, socket.id, 'consumer')
            producerArr = removeItems(producerArr, uid, socket.id, 'producer')
            TArr = removeItems(TArr, uid, socket.id, 'transport')
            delete peers[socket.id]

            // 如果rooms房间为空，删除该房间
            if (Object.keys(rooms).length === 0) {
                // console.log(`通过rooms房间 ${roomName} 已经被清空并删除`) 
            } else {
                // 从房间里移除socketId
                rooms[roomName] = {
                    router: rooms[roomName].router,
                    socketIdArr: rooms[roomName].socketIdArr.filter(socketId => socketId !== socket.id)
                }
            }
        } else {
            console.log(`peers中${socket.id}主动离开断开连接`)
        }
    })


    socket.on('error', (err) => {
        console.error("socket连接发生错误: " + err)
    })

    // ----上面socket调用的函数--------------------------------------------------------

    //创建生产者或消费者的webRTC通道的函数，传入当前房间的路由
    const creWebRtcT = async (router) => {
        return new Promise(async (resolve, reject) => {
            try {
                // 根据选项创建一个通用通道
                let transport = await router.createWebRtcTransport(webRtcT_Options)
                // console.log(`WebRtc通道ID: ${transport.id}`)
                // 监听状态变化并处理
                transport.on('dtlsstatechange', dtlsState => {
                    if (dtlsState === 'closed') {
                        transport.close()
                    }
                })
                // 监听通道关闭
                transport.on('close', () => {
                    // console.log('通道关闭')
                })
                //返回通道参数
                resolve(transport)
            } catch (error) { reject(error) }
        })
    }

    const addT = (transport, roomName, consumer, uid) => {
        //TArr通道数组追加新的通道
        TArr.push({ uid, socketId: socket.id, transport, roomName, consumer })
        //用户对等连接对象peers里的当前连接追加transports
        peers[socket.id] = {
            ...peers[socket.id],
            uid,
            // transports里追加通道ID
            transports: [
                ...peers[socket.id].transports,
                transport.id,
            ]
        }
    }

    const addProducer = (producer, roomName, uid) => {
        //生产者数组追加新的生产者
        producerArr.push({ uid, socketId: socket.id, producer, roomName })
        //用户对等连接对象perrs里的当前连接追加producers
        peers[socket.id] = {
            ...peers[socket.id],
            uid,
            producers: [
                ...peers[socket.id].producers,
                producer.id,
            ]
        }
    }

    const addConsumer = (consumer, roomName, uid) => {
        //消费者数组追加新的消费者
        consumerArr.push({ uid, socketId: socket.id, consumer, roomName })

        //用户对等连接对象perrs里的当前连接追加consumers
        peers[socket.id] = {
            ...peers[socket.id],
            uid,
            consumers: [
                ...peers[socket.id].consumers,
                consumer.id,
            ]
        }
    }

    //告知消费者函数
    const tellConsumers = (socketId, kind, id, roomName, newUName) => {
        // console.log(`${newUName}用户${socketId}刚刚通过生产${kind}通道${id} 加入房间${roomName}`)
        // 一个新的生产者刚刚加入，告知所有消费者
        const roomSockets = rooms[roomName].socketIdArr
        roomSockets.forEach(otherSocketId => {
            if (otherSocketId !== socketId) {
                const otherSocket = peers[otherSocketId].socket
                otherSocket.emit('new-producer', { newProducerIdObj: [id, newUName] })
            }
        })
    }


    const getTransport = (socketId) => {
        const [producerTransport] = TArr.filter(transport => transport.socketId === socketId && !transport.consumer)
        return producerTransport.transport
    }

    const removeItems = (items, uid, socketId, type) => {
        items.forEach(item => {
            if (item.uid === uid && item.socketId === socket.id) {
                item[type].close()
            }
        })
        items = items.filter(item => item.socketId !== socket.id)

        return items
    }
})
